Day 12: 다형성과 캐스팅
다형성(Polymorphism)은 같은 타입의 변수가 실제로는 다양한 형태의 객체를 참조할 수 있는 성질입니다. 마치 “동물”이라는 한 단어로 강아지, 고양이, 새 등 다양한 동물을 가리킬 수 있는 것과 같습니다. 이를 통해 유연하고 확장 가능한 코드를 작성할 수 있습니다.
업캐스팅과 다형성
자식 객체를 부모 타입 변수에 담는 것이 업캐스팅입니다. 자동으로 이루어집니다.
class Animal {
String name;
Animal(String name) {
this.name = name;
}
void speak() {
System.out.println(name + "이(가) 소리를 냅니다.");
}
void eat() {
System.out.println(name + "이(가) 먹이를 먹습니다.");
}
}
class Dog extends Animal {
Dog(String name) { super(name); }
@Override
void speak() {
System.out.println(name + ": 멍멍!");
}
void fetch() {
System.out.println(name + "이(가) 공을 물어옵니다.");
}
}
class Cat extends Animal {
Cat(String name) { super(name); }
@Override
void speak() {
System.out.println(name + ": 야옹~");
}
void scratch() {
System.out.println(name + "이(가) 발톱을 세웁니다.");
}
}
class Bird extends Animal {
Bird(String name) { super(name); }
@Override
void speak() {
System.out.println(name + ": 짹짹!");
}
void fly() {
System.out.println(name + "이(가) 하늘을 날아갑니다.");
}
}
public class PolymorphismBasic {
public static void main(String[] args) {
// 업캐스팅: 자식 객체를 부모 타입으로 참조
Animal animal1 = new Dog("바둑이");
Animal animal2 = new Cat("나비");
Animal animal3 = new Bird("짹짹이");
// 다형성: 같은 메서드 호출이지만 실제 객체에 따라 다른 동작
animal1.speak(); // 바둑이: 멍멍!
animal2.speak(); // 나비: 야옹~
animal3.speak(); // 짹짹이: 짹짹!
// 배열로 다형성 활용
Animal[] animals = {animal1, animal2, animal3};
for (Animal animal : animals) {
animal.speak(); // 각 동물의 고유 소리
animal.eat(); // 공통 메서드
}
// animal1.fetch(); // 컴파일 에러! (Animal 타입에는 fetch가 없음)
}
}
다운캐스팅과 instanceof
부모 타입 변수를 자식 타입으로 변환할 때는 명시적 캐스팅이 필요합니다.
public class DowncastingExample {
static void handleAnimal(Animal animal) {
// 공통 동작
animal.speak();
// instanceof로 실제 타입 확인 후 다운캐스팅
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.fetch(); // Dog 고유 메서드 사용 가능
} else if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.scratch();
} else if (animal instanceof Bird) {
Bird bird = (Bird) animal;
bird.fly();
}
}
// Java 16+: 패턴 매칭 instanceof
static void handleAnimalModern(Animal animal) {
animal.speak();
// 타입 확인과 변수 선언을 한 번에
if (animal instanceof Dog dog) {
dog.fetch();
} else if (animal instanceof Cat cat) {
cat.scratch();
} else if (animal instanceof Bird bird) {
bird.fly();
}
}
public static void main(String[] args) {
Animal[] animals = {
new Dog("바둑이"),
new Cat("나비"),
new Bird("짹짹이")
};
for (Animal animal : animals) {
System.out.println("--- " + animal.name + " ---");
handleAnimalModern(animal);
}
// 잘못된 다운캐스팅은 ClassCastException 발생
// Animal a = new Cat("나비");
// Dog d = (Dog) a; // ClassCastException!
}
}
다형성을 활용한 설계
결제 시스템을 다형성으로 설계하는 실전 예제입니다.
class Payment {
String payerName;
long amount;
Payment(String payerName, long amount) {
this.payerName = payerName;
this.amount = amount;
}
boolean process() {
System.out.println("기본 결제 처리");
return true;
}
void printReceipt() {
System.out.println("--- 영수증 ---");
System.out.println("결제자: " + payerName);
System.out.println("금액: " + String.format("%,d", amount) + "원");
}
}
class CreditCardPayment extends Payment {
String cardNumber;
CreditCardPayment(String payerName, long amount, String cardNumber) {
super(payerName, amount);
this.cardNumber = cardNumber;
}
@Override
boolean process() {
String masked = "****-****-****-" + cardNumber.substring(cardNumber.length() - 4);
System.out.println("신용카드 결제: " + masked);
return true;
}
}
class BankTransfer extends Payment {
String bankName;
BankTransfer(String payerName, long amount, String bankName) {
super(payerName, amount);
this.bankName = bankName;
}
@Override
boolean process() {
System.out.println(bankName + " 계좌이체 처리 중...");
return true;
}
}
class MobilePayment extends Payment {
String phoneNumber;
MobilePayment(String payerName, long amount, String phoneNumber) {
super(payerName, amount);
this.phoneNumber = phoneNumber;
}
@Override
boolean process() {
System.out.println("모바일 결제 (전화번호: " + phoneNumber + ")");
return true;
}
}
public class PaymentSystem {
// 다형성 덕분에 결제 수단에 관계없이 동일한 코드로 처리
static void processPayment(Payment payment) {
if (payment.process()) {
payment.printReceipt();
System.out.println("결제 완료!\n");
}
}
public static void main(String[] args) {
Payment[] payments = {
new CreditCardPayment("홍길동", 50000, "1234-5678-9012-3456"),
new BankTransfer("김영희", 100000, "국민은행"),
new MobilePayment("이철수", 15000, "010-1234-5678")
};
for (Payment payment : payments) {
processPayment(payment); // 모두 같은 메서드로 처리
}
}
}
sealed 클래스 (Java 17+)
상속할 수 있는 클래스를 제한하는 기능입니다.
// sealed: 허용된 자식만 상속 가능
sealed class Notification permits EmailNotification, SmsNotification, PushNotification {
String recipient;
String message;
Notification(String recipient, String message) {
this.recipient = recipient;
this.message = message;
}
void send() {
System.out.println("알림 전송: " + message);
}
}
final class EmailNotification extends Notification {
String subject;
EmailNotification(String recipient, String message, String subject) {
super(recipient, message);
this.subject = subject;
}
@Override
void send() {
System.out.println("이메일 발송 -> " + recipient + " [" + subject + "]");
}
}
final class SmsNotification extends Notification {
SmsNotification(String recipient, String message) {
super(recipient, message);
}
@Override
void send() {
System.out.println("SMS 발송 -> " + recipient);
}
}
non-sealed class PushNotification extends Notification {
PushNotification(String recipient, String message) {
super(recipient, message);
}
@Override
void send() {
System.out.println("Push 알림 -> " + recipient);
}
}
오늘의 연습문제
-
도형 계산기:
Shape배열에Circle,Rectangle,Triangle객체를 담고, 반복문으로 각 도형의 이름과 넓이를 출력하세요. 다형성을 활용하여 타입별 분기 없이 처리하세요. -
할인 정책 시스템:
DiscountPolicy부모 클래스를 만들고,PercentDiscount(비율 할인),FixedDiscount(정액 할인),NoDiscount를 자식으로 구현하세요.applyDiscount(long price)메서드를 다형성으로 처리하세요. -
동물원 시뮬레이션: 다양한 동물 객체를 배열에 담고,
instanceof(패턴 매칭)를 사용하여 동물 종류별로 특수 행동을 실행하는 프로그램을 작성하세요. 각 동물의 먹이 종류와 양도 출력하세요.